/**
 * Copyleft c==8 2024 acetone - The Samty project
 * Copyright (c) 2019-2022 polistern
 * Copyright (c) 2017 The I2P Project
 * Copyright (c) 2013-2015 The Anoncoin Core developers
 * Copyright (c) 2012-2013 giv
 *
 * Distributed under the MIT software license, see the accompanying
 * file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 */

#include <ctime>
#include <cstdlib>
#include <cstring>

#include "i2psam.h"

namespace Samty
{

#ifdef WIN32
int I2pSocket::instances_ = 0;

void I2pSocket::initWSA()
{
    WSADATA wsadata;
    int ret = WSAStartup(MAKEWORD(2, 2), &wsadata);
    if (ret != NO_ERROR)
        print_error("Failed to initialize winsock library");
}

void I2pSocket::freeWSA()
{
    WSACleanup();
}
#endif // WIN32

I2pSocket::I2pSocket(const std::string &SAMHost, uint16_t SAMPort)
    : socket_(INVALID_SOCKET), SAMHost_(SAMHost), SAMPort_(SAMPort) {
#ifdef WIN32
    if (instances_++ == 0)
        initWSA();
#endif // WIN32

    memset(&servAddr_, 0, sizeof(servAddr_));

    servAddr_.sin_family = AF_INET;
    servAddr_.sin_addr.s_addr = inet_addr(SAMHost.c_str());
    servAddr_.sin_port = htons(SAMPort);
    bootstrapI2P();
}

I2pSocket::I2pSocket(const sockaddr_in &addr)
    : socket_(INVALID_SOCKET), servAddr_(addr)
{
#ifdef WIN32
    if (instances_++ == 0)
        initWSA();
#endif // WIN32
    bootstrapI2P();
}

I2pSocket::I2pSocket(const I2pSocket &rhs)
    : socket_(INVALID_SOCKET), servAddr_(rhs.servAddr_)
{
#ifdef WIN32
    if (instances_++ == 0)
        initWSA();
#endif // WIN32
    bootstrapI2P();
}

I2pSocket::~I2pSocket()
{
    close();

#ifdef WIN32
    if (--instances_ == 0)
        freeWSA();
#endif // WIN32
}

void I2pSocket::bootstrapI2P()
{
    init();
    if (isOk())
        handshake();
}

void I2pSocket::init()
{
    /**
   * Here is where the only real OS socket is called for creation to talk with the router,
   * the value returned is stored in our variable socket_  which holds the connection
   * to our stream for everything else
   */
    socket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_ == INVALID_SOCKET)
    {
        errorString_ = "Failed to create socket";
        return;
    }

    if (connect(socket_, (const sockaddr *) &servAddr_, sizeof(servAddr_)) == SOCKET_ERROR)
    {
        close();
        errorString_ = "Failed to connect to SAM";
        return;
    }
}

SOCKET I2pSocket::release()
{
    SOCKET temp = socket_;
    socket_ = INVALID_SOCKET;
    return temp;
}

// If the handshake works, we're talking to a valid I2P router.
void I2pSocket::handshake()
{
    this->write(Message::hello(minVer_, maxVer_));
    const std::string answer = this->readString();
    const Message::eStatus answerStatus = Message::checkAnswer(answer);
    if (answerStatus == Message::OK)
        version_ = Message::getValue(answer, "VERSION");
    else
        errorString_ = "Handshake failed";
}

bool I2pSocket::write(const std::string &msg)
{
    if (!isOk())
    {
        errorString_ = "Failed to send data because socket is closed";
        return false;
    }

    ssize_t sentBytes = send(socket_, msg.c_str(), msg.length(), 0);
    if (sentBytes == SOCKET_ERROR)
    {
        close();
        errorString_ = "Failed to send data";
        return false;
    }
    if (sentBytes == 0)
    {
        close();
        errorString_ = "I2pSocket was closed";
        return false;
    }

    return true;
}

bool I2pSocket::write(const std::vector<uint8_t> &data)
{
    if (!isOk())
    {
        errorString_ = "Failed to send data because socket is closed";
        return false;
    }

    ssize_t sentBytes = send(socket_, data.data(), data.size(), 0);
    if (sentBytes == SOCKET_ERROR)
    {
        close();
        errorString_ = "Failed to send data";
        return false;
    }
    if (sentBytes == 0)
    {
        close();
        errorString_ = "I2pSocket was closed";
        return false;
    }

    return true;
}

std::string I2pSocket::readDestination()
{
    std::string certificate;

    char byte = 0;
    while (true)
    {
        ssize_t bytes_received = recv(socket_, &byte, 1, 0);
        if (bytes_received < 1)
        {
            return std::string();
        }

        if (byte == '\n') break;

        certificate += byte;
    }

    Samty::Identity guestIdent;
    guestIdent.init(certificate);
    return guestIdent.dest32();
}

std::vector<uint8_t> I2pSocket::readBinary()
{
    std::vector<uint8_t> result (SAM_BUFSIZE);
    if (!isOk())
    {
        errorString_ = "Failed to read data because socket is closed";
        return result;
    }
    ssize_t recievedBytes = recv(socket_, result.data(), SAM_BUFSIZE, 0);
    if (recievedBytes == SOCKET_ERROR)
    {
        close();
        errorString_ = "Failed to receive data";
        return result;
    }
    if (recievedBytes == 0)
    {
        close();
        errorString_ = "I2pSocket was closed";
    }

    return result;
}

ssize_t I2pSocket::readBytes(uint8_t *buffer, size_t length, int timeoutSec)
{
    if (!isOk())
    {
        errorString_ = "Failed to read data because socket is closed";
        return -1;
    }

#ifdef WIN32

    HANDLE socketEvent = WSACreateEvent();
    if (socketEvent == WSA_INVALID_EVENT)
    {
        errorString_ = "Failed to create event: " + std::to_string(WSAGetLastError());
        return -1;
    }

    WSAEventSelect(socket_, socketEvent, FD_READ);

    DWORD result = WSAWaitForMultipleEvents(1, &socketEvent, FALSE, timeoutMs, FALSE);

    if (result == WSA_WAIT_TIMEOUT)
    {
        WSACloseEvent(socketEvent);
        errorString_ = "Read timed out";
        return 0;
    }
    else if (result == WSA_WAIT_FAILED)
    {
        WSACloseEvent(socketEvent);
        errorString_ = "WSAWaitForMultipleEvents failed: " + std::to_string(WSAGetLastError());
        return -1;
    }

    WSACloseEvent(socketEvent);

    // Если достигнуто событие FD_READ
    ssize_t totalBytesRead = 0;
    ssize_t bytesRead = 0;

    while (totalBytesRead < length)
    {
        bytesRead = recv(socket_, reinterpret_cast<char*>(buffer + totalBytesRead), length - totalBytesRead, 0);
        if (bytesRead > 0)
        {
            totalBytesRead += bytesRead;
        }
        else if (bytesRead == 0)
        {
            // Соединение закрыто
            return totalBytesRead;
        }
        else
        {
            // Ошибка recv
            errorString_ = "Recv failed: " + std::to_string(WSAGetLastError());
            return -1;
        }
    }

    return totalBytesRead;

#else

    fd_set readfds;
    struct timeval timeout;
    int result;

    FD_ZERO(&readfds);
    FD_SET(socket_, &readfds);

    timeout.tv_sec = timeoutSec;
    timeout.tv_usec = 0;

    result = select(socket_ + 1, &readfds, nullptr, nullptr, &timeout);

    if (result == -1)
    {
        errorString_ = "Select failed: " + std::string(strerror(errno));
        return -1;
    }
    else if (result == 0)
    {
        errorString_ = "Read timed out";
        return 0;
    }

    if (FD_ISSET(socket_, &readfds))
    {
        ssize_t totalBytesRead = 0;
        ssize_t bytesRead = 0;

        while (totalBytesRead < length)
        {
            bytesRead = recv(socket_, buffer + totalBytesRead, length - totalBytesRead, 0);
            if (bytesRead > 0)
            {
                totalBytesRead += bytesRead;
            }
            else if (bytesRead == 0)
            {
                return totalBytesRead;
            }
            else
            {
                errorString_ = "Recv failed: " + std::string(strerror(errno));
                return -1;
            }
        }

        return totalBytesRead;
    }

    errorString_ = "Socket not ready for reading";
    return -1;

#endif // if not WIN32
}

std::string I2pSocket::readString()
{
    if (!isOk())
    {
        errorString_ = "Failed to read data because socket is closed";
        return std::string();
    }
    char buffer[SAM_BUFSIZE];
    memset(buffer, 0, SAM_BUFSIZE);
    ssize_t recievedBytes = recv(socket_, buffer, SAM_BUFSIZE-1, 0);
    if (recievedBytes == SOCKET_ERROR)
    {
        close();
        errorString_ = "Failed to receive data";
        return std::string();
    }
    if (recievedBytes == 0)
    {
        close();
        errorString_ = "I2pSocket was closed";
    }

    return std::string(buffer);
}

void I2pSocket::close()
{
    if (socket_ != INVALID_SOCKET)
    {
#ifdef WIN32
        ::closesocket(socket_);
#else
        ::close(socket_);
#endif // WIN32
        socket_ = INVALID_SOCKET;
    }
}

bool I2pSocket::isOk() const { return socket_ != INVALID_SOCKET; }

const std::string &I2pSocket::getHost() const { return SAMHost_; }

uint16_t I2pSocket::getPort() const { return SAMPort_; }

const std::string &I2pSocket::getVersion() const { return version_; }

const sockaddr_in &I2pSocket::getAddress() const { return servAddr_; }

//-----------------------------------------------------------------------------

SAMSession::SAMSession(const Configuration& configuration)
    : socket_(configuration.SAMHost, configuration.SAMPort),
    sessionID_(generateSessionID()),
    isSick_(false),
    config_(configuration)
{
    i2pOptions_ = "i2cp.leaseSetEncType=4";

    if (not configuration.publishLeaseSet)
    {
        i2pOptions_ += " i2cp.dontPublishLeaseSet";
    }

    if (configuration.encryptedLeaseSet)
    {
        i2pOptions_ += " i2cp.leaseSetType=5";
    }

    i2pOptions_ += " inbound.quantity=" + std::to_string(configuration.inboundQuantity);
    i2pOptions_ += " inbound.length=" + std::to_string(configuration.inboundLength);
    i2pOptions_ += " inbound.lengthVariance=" + std::to_string(configuration.inboundVariance);
    i2pOptions_ += " outbound.quantity=" + std::to_string(configuration.outboundQuantity);
    i2pOptions_ += " outbound.length=" + std::to_string(configuration.outboundLength);
    i2pOptions_ += " outbound.lengthVariance=" + std::to_string(configuration.outboundVariance);
}

SAMSession::SAMSession(SAMSession& rhs)
    : socket_(rhs.socket_),
    sessionID_(generateSessionID()),
    myDestination_(rhs.myDestination_),
    i2pOptions_(rhs.i2pOptions_),
    isSick_(false),
    config_(rhs.config_)
{
    rhs.fallSick();
    rhs.socket_.close();
}

/*static*/
std::string SAMSession::generateSessionID()
{
    static const int minSessionIDLength = 5;
    static const int maxSessionIDLength = 9;
    static const char sessionIDAlphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int length = minSessionIDLength - 1;
    std::string result;

    srand(time(nullptr));

    while (length < minSessionIDLength)
        length = rand() % maxSessionIDLength;

    while (length-- > 0)
        result += sessionIDAlphabet[rand() % (sizeof(sessionIDAlphabet) - 1)];

    return result;
}

RequestResult<const std::string> SAMSession::namingLookup(const std::string &name) const
{
    typedef RequestResult<const std::string> ResultType;
    typedef Message::Answer<const std::string> AnswerType;

    if (name.find(".b32.i2p") != std::string::npos)
    {
        return ResultType(name);
    }

    std::unique_ptr<I2pSocket> newSocket(new I2pSocket(socket_));
    const AnswerType answer = namingLookup(*newSocket, name);
    switch (answer.status)
    {
    case Message::OK:
        return ResultType(answer.value);
    case Message::EMPTY_ANSWER:
    case Message::CLOSED_SOCKET:
        fallSick();
        break;
    default:
        break;
    }
    return ResultType();
}

Identity SAMSession::createSession(const std::string& destination)
{
    return createSession(destination, SAM_DEFAULT_SIGNATURE_TYPE);
}

Identity SAMSession::createSession(const std::string& destination, const std::string& sigType)
{
    return createSession(destination, sigType, i2pOptions_);
}

void SAMSession::fallSick() const { isSick_ = true; }

/*static*/
Message::Answer<const std::string> SAMSession::rawRequest(I2pSocket &socket, const std::string &requestStr)
{
    typedef Message::Answer<const std::string> AnswerType;

    if (!socket.isOk())
        return AnswerType(Message::CLOSED_SOCKET);
    socket.write(requestStr);
    const std::string answer = socket.readString();
    const Message::eStatus status = Message::checkAnswer(answer);
    return AnswerType(status, answer);
}

/*static*/
Message::Answer<const std::string> SAMSession::request(I2pSocket &socket,
                                                       const std::string &requestStr,
                                                       const std::string &keyOnSuccess)
{
    typedef Message::Answer<const std::string> AnswerType;

    const AnswerType answer = rawRequest(socket, requestStr);
    return (answer.status == Message::OK) ? AnswerType(answer.status, Message::getValue(answer.value, keyOnSuccess))
                                          : answer;
}

/*static*/
Message::eStatus SAMSession::request(I2pSocket &socket, const std::string &requestStr)
{
    return rawRequest(socket, requestStr).status;
}

/*static*/
Message::Answer<const std::string> SAMSession::namingLookup(I2pSocket &socket, const std::string &name)
{
    return request(socket, Message::namingLookup(name), "VALUE");
}

//-----------------------------------------------------------------------------

StreamSession::StreamSession(const Configuration& configuration)
    : SAMSession(configuration)
{
    const std::string sigType = configuration.encryptedLeaseSet ? "11" : "7";
    myDestination_ = createStreamSession(configuration.destination, sigType, i2pOptions_);
}

StreamSession::StreamSession(StreamSession &rhs)
    : SAMSession(rhs)
{
    rhs.fallSick();
    rhs.socket_.close();
    const std::string sigType = rhs.config_.encryptedLeaseSet ? "11" : "7";
    (void) createStreamSession(myDestination_.fullBase64(), sigType, rhs.i2pOptions_);

    for (const auto &forwardedStream : rhs.forwardedStreams_)
        forward(forwardedStream.host, forwardedStream.port, forwardedStream.silent);
}

StreamSession::~StreamSession()
{
    stopForwardingAll();
}

RequestResult<std::unique_ptr<I2pSocket>> StreamSession::accept(bool silent)
{
    typedef RequestResult<std::unique_ptr<I2pSocket>> ResultType;

    std::unique_ptr<I2pSocket> streamSocket(new I2pSocket(socket_));
    const Message::eStatus status = accept(*streamSocket, sessionID_, silent);
    switch (status)
    {
    case Message::OK:
        return ResultType(std::move(streamSocket));
    case Message::EMPTY_ANSWER:
    case Message::CLOSED_SOCKET:
    case Message::INVALID_ID:
    case Message::I2P_ERROR:
        fallSick();
        break;
    default:
        break;
    }
    return ResultType();
}

RequestResult<std::unique_ptr<I2pSocket>> StreamSession::connect(const std::string &destination, bool silent)
{
    typedef RequestResult<std::unique_ptr<I2pSocket>> ResultType;

    std::unique_ptr<I2pSocket> streamSocket(new I2pSocket(socket_));
    const Message::eStatus status = connect(*streamSocket, sessionID_, destination, silent);

    if (silent)
        return ResultType(std::move(streamSocket));

    switch (status)
    {
    case Message::OK:
        return ResultType(std::move(streamSocket));
    case Message::EMPTY_ANSWER:
    case Message::CLOSED_SOCKET:
    case Message::INVALID_ID:
    case Message::I2P_ERROR:
        fallSick();
        break;
    default:
        break;
    }
    return ResultType();
}

RequestResult<void> StreamSession::forward(const std::string &host, uint16_t port, bool silent)
{
    typedef RequestResult<void> ResultType;

    std::unique_ptr<I2pSocket> newSocket(new I2pSocket(socket_));
    const Message::eStatus status = forward(*newSocket, sessionID_, host, port, silent);
    switch (status)
    {
    case Message::OK:
        forwardedStreams_.push_back(ForwardedStream(newSocket.get(), host, port, silent));
        newSocket.release(); // release after successful push_back only
        return ResultType(true);
    case Message::EMPTY_ANSWER:
    case Message::CLOSED_SOCKET:
    case Message::INVALID_ID:
    case Message::I2P_ERROR:
        fallSick();
        break;
    default:
        break;
    }
    return ResultType();
}

void StreamSession::closeSocket()
{
    socket_.close();
}

std::string StreamSession::errorString() const
{
    return socket_.errorString();
}

Identity StreamSession::createStreamSession(const std::string& destination, const std::string& sigType, const std::string& i2pOptions)
{
    typedef Message::Answer<const std::string> AnswerType;

    const AnswerType
        answer = createStreamSession(socket_, sessionID_, config_.nickname, destination, i2pOptions, sigType);
    if (answer.status != Message::OK)
    {
        fallSick();
        return Identity();
    }
    Identity ident;
    ident.init(answer.value, sigType == SAM_BLINDED_SIGNATURE_TYPE);
    return ident;
}

Identity StreamSession::createSession(const std::string& destination, const std::string& sigType, const std::string& i2pOptions)
{
    typedef Message::Answer<const std::string> AnswerType;

    const AnswerType
        answer = createStreamSession(socket_, sessionID_, config_.nickname, destination, i2pOptions, sigType);
    if (answer.status != Message::OK)
    {
        fallSick();
        return Identity();
    }

    Identity ident;
    ident.init(answer.value);
    return ident;
}

void StreamSession::stopForwarding(const std::string &host, uint16_t port)
{
    for (auto it = forwardedStreams_.begin(); it != forwardedStreams_.end();)
    {
        if (it->port == port && it->host == host)
        {
            delete (it->socket);
            it = forwardedStreams_.erase(it);
        }
        else
            ++it;
    }
}

void StreamSession::stopForwardingAll()
{
    for (auto &forwardedStream : forwardedStreams_)
        delete (forwardedStream.socket);
    forwardedStreams_.clear();
    socket_.close();
}

/*static*/
Message::Answer<const std::string> StreamSession::createStreamSession(I2pSocket &socket,
                                                                      const std::string &sessionID,
                                                                      const std::string &nickname,
                                                                      const std::string &destination,
                                                                      const std::string &options,
                                                                      const std::string &signatureType)
{
    return request(socket,
                   Message::sessionCreate(sessionID, nickname, destination, options, signatureType),
                   "DESTINATION");
}

/*static*/
Message::eStatus StreamSession::accept(I2pSocket &socket, const std::string &sessionID, bool silent)
{
    return request(socket, Message::streamAccept(sessionID, silent));
}

/*static*/
Message::eStatus StreamSession::connect(I2pSocket &socket,
                                        const std::string &sessionID,
                                        const std::string &destination,
                                        bool silent)
{
    return request(socket, Message::streamConnect(sessionID, destination, silent));
}

/*static*/
Message::eStatus StreamSession::forward(I2pSocket &socket,
                                        const std::string &sessionID,
                                        const std::string &host,
                                        uint16_t port,
                                        bool silent)
{
    return request(socket, Message::streamForward(sessionID, host, port, silent));
}

//-----------------------------------------------------------------------------

std::string Message::hello(const std::string &minVer, const std::string &maxVer)
{
   /**
   * ->  HELLO VERSION
   *        MIN=$min
   *        MAX=$max
   *
   * <-  HELLO REPLY
   *        RESULT=OK
   *        VERSION=$version
   */

    static constexpr char helloFormat[] = "HELLO VERSION MIN=%s MAX=%s\n";
    return createSAMRequest(helloFormat, minVer.c_str(), maxVer.c_str());
}

std::string Message::sessionCreate(const std::string &sessionID,
                                   const std::string &nickname,
                                   const std::string &destination   /*= SAM_DEFAULT_DESTINATION*/,
                                   const std::string &options       /*= ""*/,
                                   const std::string &signatureType /*= SAM_DEFAULT_SIGNATURE_TYPE*/)
{
    /**
   * ->  SESSION CREATE
   *        STYLE={STREAM,DATAGRAM,RAW}
   *        ID={$nickname}
   *        DESTINATION={$private_destination_key,TRANSIENT}
   *        [option=value]*
   *
   * <-  SESSION STATUS
   *        RESULT=OK
   *        DESTINATION=$private_destination_key
   */

    static constexpr char sessionCreateFormat[] =
        "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s SIGNATURE_TYPE=%s inbound.nickname=%s %s\n";
    return createSAMRequest(sessionCreateFormat,
                            "STREAM",
                            sessionID.c_str(),
                            destination.c_str(),
                            signatureType.c_str(),
                            nickname.c_str(),
                            options.c_str());
}

std::string Message::streamAccept(const std::string &sessionID,
                                  bool silent /*= false*/)
{
    /**
   * -> STREAM ACCEPT
   *       ID={$nickname}
   *       [SILENT={true,false}]
   *
   * <- STREAM STATUS
   *       RESULT=$result
   *       [MESSAGE=...]
   */

    static constexpr char streamAcceptFormat[] = "STREAM ACCEPT ID=%s SILENT=%s\n";
    return createSAMRequest(streamAcceptFormat,
                            sessionID.c_str(),
                            silent ? "true" : "false");
}

std::string Message::streamConnect(const std::string &sessionID,
                                   const std::string &destination,
                                   bool silent /*= false*/)
{
    /**
   * -> STREAM CONNECT
   *       ID={$nickname}
   *       DESTINATION=$peer_public_base64_key
   *       [SILENT={true,false}]
   *
   * <- STREAM STATUS
   *       RESULT=$result
   *       [MESSAGE=...]
   */

    static constexpr char streamConnectFormat[] = "STREAM CONNECT ID=%s DESTINATION=%s SILENT=%s\n";
    return createSAMRequest(streamConnectFormat,
                            sessionID.c_str(),
                            destination.c_str(),
                            silent ? "true" : "false");
}

std::string Message::streamForward(const std::string &sessionID,
                                   const std::string &host,
                                   uint16_t port,
                                   bool silent /*= false*/)
{
    /**
   * -> STREAM FORWARD
   *       ID={$nickname}
   *       PORT={$port}
   *       [HOST={$host}]
   *       [SILENT={true,false}]
   *
   * <- STREAM STATUS
   *       RESULT=$result
   *       [MESSAGE=...]
   */
    static constexpr char streamForwardFormat[] = "STREAM FORWARD ID=%s PORT=%u HOST=%s SILENT=%s\n";
    return createSAMRequest(streamForwardFormat,
                            sessionID.c_str(),
                            (unsigned) port,
                            host.c_str(),
                            silent ? "true" : "false");
}

std::string Message::namingLookup(const std::string &name)
{
    if (name.find(".b32.i2p") != std::string::npos) return name;

    /**
   * -> NAMING LOOKUP
   *       NAME=$name
   *
   * <- NAMING REPLY
   *       RESULT=OK
   *       NAME=$name
   *       VALUE=$base64key
   */

    static constexpr char namingLookupFormat[] = "NAMING LOOKUP NAME=%s\n";
    return createSAMRequest(namingLookupFormat,
                            name.c_str());
}

#define SAM_MAKESTRING(X) SAM_MAKESTRING2(X)
#define SAM_MAKESTRING2(X) #X

#define SAM_CHECK_RESULT(value) \
if (result == SAM_MAKESTRING(value)) \
        return value

Message::eStatus Message::checkAnswer(const std::string &answer) {
    if (answer.empty())
        return EMPTY_ANSWER;

    const std::string result = getValue(answer, "RESULT");

    SAM_CHECK_RESULT(OK);
    SAM_CHECK_RESULT(DUPLICATED_DEST);
    SAM_CHECK_RESULT(DUPLICATED_ID);
    SAM_CHECK_RESULT(I2P_ERROR);
    SAM_CHECK_RESULT(INVALID_ID);
    SAM_CHECK_RESULT(INVALID_KEY);
    SAM_CHECK_RESULT(CANT_REACH_PEER);
    SAM_CHECK_RESULT(TIMEOUT);
    SAM_CHECK_RESULT(NOVERSION);
    SAM_CHECK_RESULT(KEY_NOT_FOUND);
    SAM_CHECK_RESULT(PEER_NOT_FOUND);
    SAM_CHECK_RESULT(ALREADY_ACCEPTING);

    return CANNOT_PARSE_ERROR;
}

#undef SAM_CHECK_RESULT
#undef SAM_MAKESTRING2
#undef SAM_MAKESTRING

std::string Message::getValue(const std::string &answer, const std::string &key) {
    if (key.empty())
        return std::string();

    const std::string keyPattern = key + "=";
    size_t valueStart = answer.find(keyPattern);
    if (valueStart == std::string::npos)
        return std::string();

    valueStart += keyPattern.length();
    size_t valueEnd = answer.find_first_of(' ', valueStart);
    if (valueEnd == std::string::npos)
        valueEnd = answer.find_first_of('\n', valueStart);
    return answer.substr(valueStart, valueEnd - valueStart);
}

} // namespace Samty
